package com.hyw.encryption.advice;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;

import com.hyw.encryption.annotation.Decrypted;
import com.hyw.encryption.configuration.EncryptionSkipperConfig;
import com.hyw.encryption.provider.IDecryptionProvider;
import com.hyw.encryption.provider.extractor.IDecryptKeyExtract;

/**
 * 用于处理请求体中的json做解密
 * 
 * @author Hongyu
 */
@ControllerAdvice
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {

  /**
   * 之所以会出现多种处理器,主要原因是系统在业务上, 可能存在多种不同的算法加解密方式.
   * <p>
   * <b>由于处理顺序的问题,系统会遍历的方式执行</b>
   */
  private Map<String, List<IDecryptionProvider>> decryptors;

  @SuppressWarnings("rawtypes")
  private IDecryptKeyExtract keyExtractor;

  private EncryptionSkipperConfig skipper;

  @SuppressWarnings({ "rawtypes" })
  public DecryptRequestBodyAdvice(List<IDecryptionProvider> decryptors, IDecryptKeyExtract keyExtractor,
      EncryptionSkipperConfig skipper) {
    Assert.isTrue(!CollectionUtils.isEmpty(decryptors), "至少要有一个解密处理器");
    this.decryptors = decryptors.stream().collect(Collectors.groupingBy(IDecryptionProvider::getAlgorithm));
    this.keyExtractor = keyExtractor;
    this.skipper = skipper;
  }

  @Override
  public boolean supports(MethodParameter methodParameter, Type targetType,
      Class<? extends HttpMessageConverter<?>> converterType) {
    return methodParameter.hasMethodAnnotation(Decrypted.class);
  }

  @Override
  public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
      Class<? extends HttpMessageConverter<?>> converterType) {
    return body;
  }

  @SuppressWarnings("unchecked")
  @Override
  public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
      Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
    Decrypted annotaion = parameter.getMethodAnnotation(Decrypted.class);
    Assert.isTrue(StringUtils.hasText(annotaion.value()), "Decrypted注解中的算法值不允许为空");
    Assert.isTrue(decryptors.containsKey(annotaion.value()), String.format("找不到算法类型为[%s]对应的解密处理器", annotaion.value()));

    if (skipper.getEnable()
        && Objects.equals(inputMessage.getHeaders().getFirst(skipper.getKey()), skipper.getValue())) {
      return inputMessage;
    } else {
      String key = null;
      if (StringUtils.hasText(annotaion.key())) {
        key = keyExtractor.extract(inputMessage.getHeaders(), annotaion.key());
        Assert.isTrue(StringUtils.hasText(key), String.format("请求头上缺少对应的参数[%s]", annotaion.key()));
      }
      ByteArrayOutputStream stream = new ByteArrayOutputStream(64);
      StreamUtils.copy(inputMessage.getBody(), stream);
      byte[] data = Base64.getDecoder().decode(stream.toByteArray());
      for (IDecryptionProvider processor : decryptors.get(annotaion.value())) {
        data = processor.decryptByKey(key, data);
      }
      return new DecryptHttpInputMessage(inputMessage, data);
    }
  }

  @Override
  public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
      Class<? extends HttpMessageConverter<?>> converterType) {
    return body;
  }

  static class DecryptHttpInputMessage implements HttpInputMessage {
    private static final Logger log = LoggerFactory.getLogger(DecryptHttpInputMessage.class);
    private HttpHeaders headers;
    private InputStream body;

    public DecryptHttpInputMessage(HttpInputMessage inputMessage, byte[] content) {
      Objects.requireNonNull(content, "输入值不允许为空");
      log.debug("解密后的内容:{}", content);
      this.headers = inputMessage.getHeaders();
      this.body = new ByteArrayInputStream(content);
    }

    @Override
    public HttpHeaders getHeaders() {
      return headers;
    }

    @Override
    public InputStream getBody() throws IOException {
      return body;
    }

  }

}
